﻿using MvvmCross.Core.ViewModels;
using Sidvall.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Sidvall.UI.Views
{
    public class RegionCollection : System.Collections.ObjectModel.KeyedCollection<string, Region>
    {
        #region Public Members

        public string DefaultRegionName { get; set; }
        public IRegionManager DefaultRegionManager { get; set; }

        #region Add

        public void Add(string name, IRegionManager regionManager)
        {
            Region item;

            item = new Region()
            {
                Name = name,
                RegionManager = regionManager
            };
            Add(item);
        }

        #endregion
        #region GetRegionViews

        public IEnumerable<object> GetRegionViews(object container, string regionName, bool includeChildRegions)
        {
            List<object> items;

            items = new List<object>();
            if (string.IsNullOrWhiteSpace(regionName))
                regionName = this.DefaultRegionName;
            Initialize(items, container, regionName);
            if (includeChildRegions)
            {
                foreach (var activeRegion in this.ActiveRegions)
                {
                    if (activeRegion.StartsWith(regionName + RegionPartNameSeparator, System.StringComparison.OrdinalIgnoreCase))
                        Initialize(items, container, activeRegion);
                }
            }
            return items;
        }

        #endregion
        #region NotifyContainerViewsAsync

        public async Task<ViewEventArgs> NotifyContainerViewsAsync(ViewEventType eventType, object container)
        {
            var views = GetContainerViews(container);
            return await ViewManager.NotifyViewsAsync(eventType, views);
        }

        #endregion
        #region RemoveViewAsync

        public async Task<bool> RemoveViewAsync(object container, string regionName)
        {
            IRegionManager regionManager;
            ViewEventArgs viewEventArgs;
            IEnumerable<object> views;
            string containerRegionName;
            object regionContainer;
            bool result;

            // Notify region views
            views = GetRegionViews(container, regionName, true);
            viewEventArgs = await ViewManager.NotifyViewsAsync(ViewEventType.ViewUnloading, views);
            if (viewEventArgs.Cancel)
                return false;

            // Remove region views
            if (string.IsNullOrWhiteSpace(regionName))
                regionName = this.DefaultRegionName;
            Initialize(container, regionName, out regionManager, out regionContainer, out containerRegionName);
            result = regionManager.RemoveView(regionContainer, containerRegionName);
            RemoveActiveRegion(regionName);
            await ViewManager.NotifyViewsAsync(ViewEventType.ViewUnloaded, views);

            return result;
        }

        #endregion
        #region ShowViewAsync

        public async Task<Region> ShowViewAsync(object container, string regionName, IViewFactory viewFactory, MvxViewModelRequest request)
        {
            return await ShowViewAsync(container, regionName, viewFactory, request, true, true, true);
        }
        internal async Task<Region> ShowViewAsync(RegionView item)
        {
            if (item == null)
                return null;
            return await ShowViewAsync(item.Container, item.RegionName, item.ViewFactory, item.Request, false, false, false);
        }
        private async Task<Region> ShowViewAsync(object container, string regionName, IViewFactory viewFactory, MvxViewModelRequest request,
            bool notifyViewUnloading, bool showQueueItems, bool canAddToQueue)
        {
            IRegionManager regionManager;
            string containerRegionName;
            object regionContainer;

            // Notify views
            var views = GetRegionViews(container, regionName, true);
            if (notifyViewUnloading)
            {
                var viewEventArgs = await ViewManager.NotifyViewsAsync(ViewEventType.ViewUnloading, views);
                if (viewEventArgs.Cancel)
                    return null;
            }

            // Show view
            if (string.IsNullOrWhiteSpace(regionName))
                regionName = this.DefaultRegionName;
            Initialize(container, regionName, out regionManager, out regionContainer, out containerRegionName);
            if (regionManager.CanShowView(regionContainer, containerRegionName))
            {
                // Remove region views
                regionManager.RemoveView(regionContainer, containerRegionName);
                RemoveActiveRegion(regionName);
                await ViewManager.NotifyViewsAsync(ViewEventType.ViewUnloaded, views);

                // Create and show view view
                var view = viewFactory.CreateView(request);
                var viewModel = ViewManager.GetViewModel(view);
                if (viewModel != null)
                {
                    viewModel.ViewModelView = view as IView;
                    viewModel.ViewModelRegionName = regionName;
                }
                await ViewManager.NotifyViewAsync(ViewEventType.ViewCreated, view, regionName);
                regionManager.ShowView(regionContainer, containerRegionName, view);
                AddActiveRegion(regionName);

                if (showQueueItems)
                    await this.RegionViews.ShowViewsAsync(this);
                return new Region()
                {
                    Name = regionName,
                    RegionManager = regionManager
                };
            }
            else if (canAddToQueue)
            {
                if (regionName.Length > containerRegionName.Length)
                    this.RegionViews.Add(container, regionName, viewFactory, request);
            }
            return null;
        }

        #endregion

        #region GetKeyForItem

        protected override string GetKeyForItem(Region item)
        {
            if (item == null)
                throw new System.ArgumentNullException("item");
            return item.Name;
        }

        #endregion

        #endregion
        #region Private Members

        private const string RegionPartNameSeparator = ".";

        #region ActiveRegions

        private HashSet<string> _ActiveRegions;
        private HashSet<string> ActiveRegions
        {
            get
            {
                if (_ActiveRegions == null)
                    _ActiveRegions = new HashSet<string>(System.StringComparer.OrdinalIgnoreCase);
                return _ActiveRegions;
            }
        }

        #endregion
        #region RegionViews

        private RegionViewCollection _RegionViews;
        private RegionViewCollection RegionViews
        {
            get
            {
                if (_RegionViews == null)
                    _RegionViews = new RegionViewCollection();
                return _RegionViews;
            }
        }

        #endregion

        #region AddActiveRegion

        private void AddActiveRegion(string regionName)
        {
            if (this.ActiveRegions.Contains(regionName))
                return;
            this.ActiveRegions.Add(regionName);
        }

        #endregion
        #region GetContainerViews

        private IEnumerable<object> GetContainerViews(object container)
        {
            List<object> viewList;
            IEnumerable<object> views;

            viewList = new System.Collections.Generic.List<object>();
            foreach (var activeRegion in this.ActiveRegions)
            {
                views = GetRegionViews(container, activeRegion, false);
                if ((views != null) && (views.Any()))
                    viewList.AddRange(views);
            }
            return viewList.ToArray();
        }

        #endregion
        #region GetRegionContainer

        private object GetRegionContainer(object container, string[] regionNameParts, int count)
        {
            IRegionManager regionManager;
            string regionName, regionNamePart;

            regionName = string.Empty;
            for (int i = 0; i < count; i++)
            {
                regionNamePart = regionNameParts[i];
                if (!string.IsNullOrWhiteSpace(regionName))
                    regionName += RegionPartNameSeparator;
                regionName += regionNamePart;

                // Get container
                regionManager = GetRegionManager(regionName);
                container = regionManager.GetRegionContainer(container, regionNamePart);
                if (container == null)
                    return null;
            }
            return container;
        }

        #endregion
        #region GetRegionManager

        private IRegionManager GetRegionManager(string regionName)
        {
            IRegionManager manager;

            if (Contains(regionName))
            {
                manager = this[regionName].RegionManager;
                if (manager != null)
                    return manager;
            }
            return this.DefaultRegionManager;
        }

        #endregion
        #region Initialize

        private void Initialize(List<object> items, object container, string regionName)
        {
            IRegionManager regionManager;
            string containerRegionName;
            object[] views;
            object regionContainer;

            Initialize(container, regionName, out regionManager, out regionContainer, out containerRegionName);
            views = regionManager.GetViews(regionContainer, containerRegionName);
            if ((views != null) && (views.Length > 0))
                items.AddRange(views);
        }
        private void Initialize(object container, string regionName, out IRegionManager regionManager, out object regionContainer, out string containerRegionName)
        {
            if (string.IsNullOrWhiteSpace(regionName))
                regionName = this.DefaultRegionName;
            var regionNameParts = regionName.Split(RegionPartNameSeparator, System.StringSplitOptions.RemoveEmptyEntries);
            regionManager = GetRegionManager(regionName);
            regionContainer = GetRegionContainer(container, regionNameParts, regionNameParts.Length - 1);
            containerRegionName = regionNameParts[regionNameParts.Length - 1];
        }

        #endregion
        #region RemoveActiveRegion

        private void RemoveActiveRegion(string regionName)
        {
            this.ActiveRegions.RemoveWhere(o =>
            {
                if (o == regionName)
                    return true;
                if (o.StartsWith(regionName + RegionPartNameSeparator, System.StringComparison.OrdinalIgnoreCase))
                    return true;
                return false;
            });
        }

        #endregion

        #endregion
        #region Constructors

        public RegionCollection(string defaultRegionName, IRegionManager defaultRegionManager)
        {
            this.DefaultRegionName = defaultRegionName;
            this.DefaultRegionManager = defaultRegionManager;
        }

        #endregion
    }
}
